package fac;

import java.awt.Color;
import java.util.Random;
import robocode.BulletHitEvent;
import robocode.BulletMissedEvent;
import robocode.HitByBulletEvent;
import robocode.HitRobotEvent;
import robocode.HitWallEvent;
import robocode.Robot;
import robocode.Rules;
import robocode.ScannedRobotEvent;
import robocode.util.Utils;

/**
 * Only interested in protecting its own existence by running away from others as long as possible.
 * Some tracking code taken from sample robot TrackFire.
 * 
 * @author Chris Fang
 */
public class ThirdRobotLaw extends Robot {

  double percentage;
  double bulletPowerRange;
  boolean startReached;
  boolean useMaxPower;
  int bulletHitCount;
  int wasHitCount;
  int wall;
  static final int LEFT_WALL = 0;
  static final int RIGHT_WALL = 1;
  int missCount;

  /**
   * Main run method.
   */
  public void run() {

    // Set spiffy new colors to be unique
    Color purple = new Color(40, 30, 70);
    Color gold = new Color(235, 220, 170);
    this.setColors(purple, gold, purple);

    setAdjustGunForRobotTurn(true);
    bulletPowerRange = Rules.MAX_BULLET_POWER - Rules.MIN_BULLET_POWER;
    while (!startReached) {
      runToStartingWall();
    }
    while (true) {
      turnGunRight(10);
    }
  }

  /**
   * When a single robot is found by the radar, track them and fire at them. If there are multiple
   * robots, move up and down at random and let them fight.
   * 
   * @param e Event generated when an enemy is scanned
   */
  @Override
  public void onScannedRobot(ScannedRobotEvent e) {
    if (startReached) {
      if ((getHeading() == 0 || getHeading() == 180)
          && (e.getBearing() < -179 || e.getBearing() > 179)) {
        runToOtherWall();
      }
      if ((getHeading() == 0 || getHeading() == 180)
          && (e.getBearing() > -1 && e.getBearing() < 1)) {
        runToOtherWall();

      }
      if (getOthers() > 1) {
        glideAtRandom();
      }
      else {
        trackAndFire(e);
      }
    }
  }

  /**
   * If a collision occurs with another robot, run to the other wall. Unless collision occurs
   * midfield, then move up or down at random before heading towards other wall.
   * 
   * @param e Event generated when collision with another occurs
   */
  @Override
  public void onHitRobot(HitRobotEvent e) {
    if (startReached) {
      runToOtherWall();
    }
    else {
      glideAtRandom();
      runToOtherWall();
    }
  }

  /**
   * Moves the robot towards the opposite wall.
   */
  private void runToOtherWall() {

    if (wall == LEFT_WALL) {
      setHeading(90);
      wall = RIGHT_WALL;
    }
    else {
      setHeading(270);
      wall = LEFT_WALL;
    }
    ahead(getBattleFieldWidth());
  }

  /**
   * Moves the robot towards the starting wall.
   */
  private void runToStartingWall() {

    startReached = false;

    if (getX() <= getBattleFieldWidth() / 2) {
      setHeading(270);
      wall = LEFT_WALL;
    }
    else {
      setHeading(90);
      wall = RIGHT_WALL;
    }
    while (!startReached) {
      ahead(500);
    }
  }

  /**
   * When hitting the opponent repeatedly, use higher power bullets.
   * 
   * @param e Event generated when a fired bullet hits an enemy
   */
  @Override
  public void onBulletHit(BulletHitEvent e) {
    missCount = 0;
    bulletHitCount++;
    if (bulletHitCount >= 3) {
      useMaxPower = true;
    }
  }

  /**
   * If a bullet misses, revert to scaling bullet power.
   * 
   * @param e Event generated when a fired bullet misses an enemy
   */
  public void onBulletMissed(BulletMissedEvent e) {
    missCount++;
    bulletHitCount = 0;
    useMaxPower = false;
  }

  /**
   * If hit by a bullet three times, attempt to evade.
   * 
   * @param e Event generated when an enemy bullet hits the robot
   */
  @Override
  public void onHitByBullet(HitByBulletEvent e) {
    wasHitCount++;
    if (wasHitCount >= 2) {
      wasHitCount = 0;
      glideAtRandom();
    }
  }

  /**
   * When the robot hits the wall, it has reached its starting position.
   * 
   * @param e Event generated when the robot hits a wall
   */
  @Override
  public void onHitWall(HitWallEvent e) {
    if (!startReached) {
      startReached = true;
    }
  }

  /**
   * Choose an amount to move at random and move the robot randomly up or down. Also compensate for
   * the movement by adjusting the gun.
   */
  public void glideAtRandom() {
    Random prng = new Random();
    int moveByThis = prng.nextInt(500);
    int turnByThis;

    if (moveByThis < 200) {
      turnByThis = 10;
    }
    else if (moveByThis >= 200 && moveByThis < 400) {
      turnByThis = 20;
    }
    else {
      turnByThis = 30;
    }

    if (prng.nextInt(2) == 0) {
      setHeading(0);
      if (wall == LEFT_WALL) {
        turnGunRight(turnByThis);
      }
      else {
        turnGunLeft(turnByThis);
      }
    }
    else {
      setHeading(180);
      if (wall == RIGHT_WALL) {
        turnGunRight(turnByThis);
      }
      else {
        turnGunLeft(turnByThis);
      }
    }
    ahead(moveByThis);
  }

  /**
   * Turns the robot towards the desired heading.
   * 
   * @param heading the heading the robot should turn towards
   */
  private void setHeading(double heading) {
    double turningFactor;

    // If the heading is invalid, just ignore it
    if (heading >= 360 || heading < 0) {
      return;
    }
    if (getHeading() <= 180 && heading - getHeading() < 180) {
      turningFactor = heading - getHeading();
    }
    else if (getHeading() <= 180) {
      turningFactor = -1 * (360 - heading + getHeading());
    }
    else if (heading - getHeading() < -180) {
      turningFactor = 360 - getHeading() + heading;
    }
    else {
      turningFactor = heading - getHeading();
    }
    turnRight(turningFactor);
  }

  /**
   * Using the generated ScannedRobotEvent, track a robot and fire when gun is lined up.
   * 
   * @param e Event generated when an enemy is scanned
   */
  private void trackAndFire(ScannedRobotEvent e) {

    // Calculate exact location of the robot
    double absoluteBearing = getHeading() + e.getBearing();
    double bearingFromGun = Utils.normalRelativeAngleDegrees(absoluteBearing - getGunHeading());

    // Fire if enemy is lined up, adjusting power based upon enemy distance
    if (Math.abs(bearingFromGun) <= 3) {
      turnGunRight(bearingFromGun);
      if (getGunHeat() == 0) {
        if (useMaxPower) {
          fire(Rules.MAX_BULLET_POWER);
        }
        else if (e.getDistance() > 100) {
          percentage = e.getDistance() / getBattleFieldWidth();
          fire(Rules.MAX_BULLET_POWER - (percentage * bulletPowerRange));
        }
        else {
          fire(Rules.MAX_BULLET_POWER);
        }
      }
      // If the enemy is too far and we miss too much, move closer
      if (missCount >= 5 && e.getDistance() > 300) {
        moveCloserToEnemy();
      }
    }
    // Otherwise just set gun to turn
    else {
      turnGunRight(bearingFromGun);
    }

    // If the target is directly in front, radar/gun may not be turning
    if (bearingFromGun == 0) {
      scan();
    }
  }

  /**
   * Turns the gun towards the desired heading.
   * 
   * @param heading the heading the gun should turn towards
   */
  private void setGunHeading(double heading) {
    double turningFactor;

    // If the heading is invalid, just ignore it
    if (heading >= 360 || heading < 0) {
      return;
    }
    if (getGunHeading() <= 180 && heading - getGunHeading() < 180) {
      turningFactor = heading - getGunHeading();
    }
    else if (getGunHeading() <= 180) {
      turningFactor = -1 * (360 - heading + getGunHeading());
    }
    else if (heading - getGunHeading() < -180) {
      turningFactor = 360 - getGunHeading() + heading;
    }
    else {
      turningFactor = heading - getGunHeading();
    }
    turnGunRight(turningFactor);
  }

  /**
   * Attempt to move closer to the enemy by going directly towards them.
   */
  private void moveCloserToEnemy() {
    if (wall == LEFT_WALL) {
      setHeading(90);
      setGunHeading(90);
    }
    else {
      setHeading(270);
      setGunHeading(270);
    }
    ahead(150);
    missCount = 0;
  }
}
